package com.jichangxiu.system.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.jichangxiu.common.constant.Constants;
import com.jichangxiu.common.entity.bo.Record;
import com.jichangxiu.common.entity.bo.ValidateBos;
import com.jichangxiu.common.exception.ServiceException;
import com.jichangxiu.common.utils.ConvertUtils;
import com.jichangxiu.framework.service.impl.BaseServiceImpl;
import com.jichangxiu.framework.utils.DynamicUtils;
import com.jichangxiu.framework.utils.NoUtils;
import com.jichangxiu.framework.utils.SecurityUtils;
import com.jichangxiu.framework.utils.ValidateUtils;
import com.jichangxiu.system.entity.bo.SysMenuBo;
import com.jichangxiu.system.entity.bo.SysPowerBo;
import com.jichangxiu.system.entity.bo.SysRoleMenuBo;
import com.jichangxiu.system.entity.dto.add.AddSysMenu;
import com.jichangxiu.system.entity.dto.edit.EditSysMenu;
import com.jichangxiu.system.entity.dto.query.QuerySysMenu;
import com.jichangxiu.system.entity.po.SysMenu;
import com.jichangxiu.system.entity.po.SysPower;
import com.jichangxiu.system.entity.po.SysRoleMenu;
import com.jichangxiu.system.entity.po.SysTenantMenu;
import com.jichangxiu.system.entity.vo.SysMenuVo;
import com.jichangxiu.system.entity.vo.SysPowerVo;
import com.jichangxiu.system.enums.MenuCategory;
import com.jichangxiu.system.mapper.SysMenuMapper;
import com.jichangxiu.system.service.SysMenuService;
import com.jichangxiu.system.service.SysPowerService;
import com.jichangxiu.system.service.SysRoleMenuService;
import com.jichangxiu.system.service.SysTenantMenuService;
import com.jichangxiu.system.utils.MenuUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * SysMenuServiceImpl:
 *
 * @author JiChangXiu
 * @create 2024-06-28 13:44:29
 */
@Service
public class SysMenuServiceImpl extends BaseServiceImpl<SysMenuMapper, SysMenu, SysMenuVo> implements SysMenuService {

    @Resource
    private SysPowerService sysPowerService;

    @Resource
    private SysRoleMenuService sysRoleMenuService;

    @Resource
    private SysTenantMenuService sysTenantMenuService;

    /**
     * 新增系统菜单
     *
     * @param addSysMenu 新增系统菜单视图对象
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysMenuVo add(AddSysMenu addSysMenu) {
        SysMenu sysMenu = addAndEditValidBo(ConvertUtils.convert(addSysMenu, SysMenuBo.class));
        sysMenu.setId(IdWorker.getIdStr());
        boolean b1 = getBaseMapper().insert(sysMenu) > 0;
        Boolean b2 = true;
        if (ObjectUtil.isNotEmpty(addSysMenu.getSysPowerDtoList()))
            b2 = sysPowerService.addOrDelOrEdit(ConvertUtils.convert(sysMenu, SysMenuBo.class), ConvertUtils.convert(addSysMenu.getSysPowerDtoList(), SysPowerBo.class));
        return b1 && b2 ? info(sysMenu.getId(), true) : null;
    }

    /**
     * 单（多）选删除系统菜单
     *
     * @param idList 系统菜单主键列表
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean del(List<String> idList) {
        boolean b1 = getBaseMapper().deleteBatchIds(delValid(idList)) > 0;
        List<SysPower> sysPowerList = sysPowerService.list(new LambdaQueryWrapper<SysPower>().in(SysPower::getMenuId, idList));
        boolean b2 = true;
        if (ObjectUtil.isNotEmpty(sysPowerList))
            b2 = sysPowerService.removeBatchByIds(sysPowerList.stream().map(SysPower::getId).collect(Collectors.toList()));
        return b1 && b2;
    }

    /**
     * 修改系统菜单
     *
     * @param editSysMenu 修改系统菜单视图对象
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysMenuVo edit(EditSysMenu editSysMenu) {
        boolean b1 = getBaseMapper().updateById(addAndEditValidBo(ConvertUtils.convert(editSysMenu, SysMenuBo.class))) > 0;
        Boolean b2 = sysPowerService.addOrDelOrEdit(ConvertUtils.convert(editSysMenu, SysMenuBo.class), ConvertUtils.convert(editSysMenu.getSysPowerDtoList(), SysPowerBo.class));
        return b1 && b2 ? info(editSysMenu.getId(), true) : null;
    }

    /**
     * 根据主键查询系统菜单
     *
     * @param id       系统菜单主键
     * @param isExtend 是否扩展
     * @return 系统菜单详情
     */
    @Override
    public SysMenuVo info(@NotBlank(message = "主键不能为空") String id, Boolean isExtend) {
        SysMenuVo sysMenuVo = getBaseMapper().selectJoinOne(SysMenuVo.class, buildQueryWrapper(QuerySysMenu.builder().id(id).build()));
        if (Boolean.TRUE.equals(isExtend) && ObjectUtil.isNotEmpty(sysMenuVo))
            sysMenuVo = renderSysMenuVoList(ListUtil.toList(sysMenuVo)).get(0);
        return sysMenuVo;
    }

    /**
     * （可分页）查询系统菜单列表
     *
     * @param querySysMenu 查询视图对象
     * @return （分页）系统菜单列表
     */
    @Override
    public Page<SysMenuVo> lists(QuerySysMenu querySysMenu) {
        Page<SysMenuVo> sysMenuVoPage;
        if (SecurityUtils.isPlatform()) {
            sysMenuVoPage = getBaseMapper().selectJoinPage(startPage(), SysMenuVo.class, buildQueryWrapper(querySysMenu));
        } else {
            List<SysTenantMenu> sysTenantMenuList = sysTenantMenuService.list(new LambdaQueryWrapper<SysTenantMenu>().in(SysTenantMenu::getTenantId, SecurityUtils.getTenantId()));
            querySysMenu.setIdList(sysTenantMenuList.stream().map(SysTenantMenu::getMenuId).collect(Collectors.toList()));
            sysMenuVoPage = getBaseMapper().selectJoinPage(startPage(), SysMenuVo.class, buildQueryWrapper(querySysMenu));
        }
        if (Boolean.TRUE.equals(querySysMenu.getIsExtend()) && ObjectUtil.isNotEmpty(sysMenuVoPage))
            sysMenuVoPage.setRecords(renderSysMenuVoList(sysMenuVoPage.getRecords()));
        return sysMenuVoPage;
    }

    /**
     * （树形）查询系统菜单列表
     *
     * @return （树形）系统菜单列表
     */
    @Override
    public List<Record> tree() {
        List<SysMenuVo> sysMenuVoList = new ArrayList<>();
        // 是平台级别租户
        if (SecurityUtils.isPlatform()) {
            // 判断当前登录用户是不是平台级别租户的管理员
            if (SecurityUtils.isTenantAdmin()) {
                // 是平台级别租户的管理员
                // 直接查询所有菜单
                sysMenuVoList = getBaseMapper().voList(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getEnabled, true));
            } else {
                // 不是平台级别租户的管理员
                // 当前登录用户所拥有的角色
                sysMenuVoList = ConvertUtils.convert(getBaseMapper().listsByUserId(SecurityUtils.getTenantId(), SecurityUtils.getUserId()), SysMenuVo.class);
            }
        }
        // 不是平台级别租户
        else {
            // 是租户级别租户的管理员
            List<SysTenantMenu> sysTenantMenuList = sysTenantMenuService.list(new LambdaQueryWrapper<SysTenantMenu>()
                    .in(SysTenantMenu::getTenantId, SecurityUtils.getTenantId())
                    .gt(SysTenantMenu::getEndDate, new Date()));
            // 普通租户去查分配给他的菜单列表
            if (ObjectUtil.isNotEmpty(sysTenantMenuList)) {
                // 判断当前登录用户是不是租户级别租户的管理员
                if (SecurityUtils.isSysAdmin()) {
                    sysMenuVoList = getBaseMapper().voList(new LambdaQueryWrapper<SysMenu>()
                            .in(SysMenu::getId, sysTenantMenuList.stream().map(SysTenantMenu::getMenuId).collect(Collectors.toList()))
                            .eq(SysMenu::getEnabled, true));
                } else {
                    // 不是租户级别租户的管理员 = 普通用户
                    DynamicUtils.changeSource(SecurityUtils.getTenantSource());
                    List<SysRoleMenuBo> sysRoleMenuBoList = sysRoleMenuService.listsByUserId(SecurityUtils.getTenantId(), ListUtil.toList(SecurityUtils.getUserId()));
                    // 查菜单，切换为默认数据源
                    DynamicUtils.setDefaultSource();
                    if (ObjectUtil.isNotEmpty(sysRoleMenuBoList)) {
                        // 比对 sysTenantMenuList 和 sysRoleMenuList
                        // sysRoleMenuList 中的每个菜单都必须在 sysTenantMenuList 中
                        sysRoleMenuBoList = sysRoleMenuBoList.stream().filter(sysRoleMenuBo -> sysTenantMenuList.stream().anyMatch(sysTenantMenu -> sysTenantMenu.getMenuId().equals(sysRoleMenuBo.getMenuId()))).collect(Collectors.toList());
                        if (ObjectUtil.isNotEmpty(sysRoleMenuBoList)) {
                            sysMenuVoList = getBaseMapper().voList(new LambdaQueryWrapper<SysMenu>()
                                    .in(SysMenu::getId, sysRoleMenuBoList.stream().map(SysRoleMenuBo::getMenuId).collect(Collectors.toList()))
                                    .eq(SysMenu::getEnabled, true));
                        }
                    }
                }
            }
        }
        return Record.listToTree(MenuUtils.listToRecord(sysMenuVoList), "id", "parentId", "children");
    }

    /**
     * 新增 / 修改 前校验
     *
     * @param sysMenuBo 校验用业务对象
     * @return 数据库操作对象
     */
    private SysMenu addAndEditValidBo(SysMenuBo sysMenuBo) {
        SysMenu sysMenu = ConvertUtils.convert(sysMenuBo, SysMenu.class);
        if (ObjectUtil.isNull(sysMenu))
            throw new ServiceException("待校验对象为空");
        if (StrUtil.isBlank(sysMenu.getParentId()))
            sysMenu.setParentId("0");
        // 新增的时候判断标识
        ArrayList<String> noDeletedMenuCodeList = ListUtil.toList(Constants.JCX_PROPERTIES.getMenuProperties().getNoDeletedMenuCodes().split(","));
        if (StrUtil.isEmpty(sysMenu.getId()) && noDeletedMenuCodeList.contains(sysMenu.getCode()))
            throw new ServiceException("菜单标识不能为 " + noDeletedMenuCodeList);
        List<ValidateBos> validateBosList = Stream.of(
                ValidateUtils.createNeValidateBos(ValidateUtils.createValidateBo(SysMenu::getId, sysMenu.getId(), "主键")),
                ValidateUtils.createOrEqValidateBos(
                        ValidateUtils.createValidateBo(SysMenu::getNo, sysMenu.getNo(), "编码"),
                        ValidateUtils.createValidateBo(SysMenu::getCode, sysMenu.getCode(), "标识（对应 vue 页面上的 name）"),
                        ValidateUtils.createValidateBo(SysMenu::getName, sysMenu.getName(), "名称（菜单显示名称）"))
        ).collect(Collectors.toList());
        // 唯一校验
        ValidateUtils.uniqueValidate(this::countWrapper, validateBosList);
        if (StrUtil.isEmpty(sysMenu.getId())) {
            // 新增
            if (MenuCategory.BUTTON.name().equalsIgnoreCase(sysMenu.getType()))
                sysMenu.setHidden(true);
            sysMenu.setNo(NoUtils.createNo(SecurityUtils.getTenantId(), "menu"));
            // 新增
            return sysMenu;
        } else {
            // 修改
            SysMenu dbSysMenu = getById(sysMenu.getId());
            if (!sysMenu.getParentId().equals(dbSysMenu.getParentId()) && !"0".equals(sysMenu.getParentId())) {
                // 父类 不能是自己
                if (sysMenu.getParentId().equals(dbSysMenu.getId()))
                    throw new ServiceException(sysMenu.getName() + "该菜单的父菜单不能是自己");
                // 如果修改了父菜单
                SysMenu parentMenu = getOne(buildQueryWrapper(QuerySysMenu.builder().id(sysMenu.getParentId()).build()));
                if (ObjectUtil.isEmpty(parentMenu))
                    throw new ServiceException("选择的父菜单不存在");
                if (MenuCategory.DIRECTORY.name().equalsIgnoreCase(sysMenu.getType())
                        || MenuCategory.MENU.name().equalsIgnoreCase(sysMenu.getType())
                        || MenuCategory.LINK.name().equalsIgnoreCase(sysMenu.getType())
                        || MenuCategory.IFRAME.name().equalsIgnoreCase(sysMenu.getType())) {
                    if (!MenuCategory.DIRECTORY.name().equalsIgnoreCase(parentMenu.getType()))
                        throw new ServiceException("目录、菜单、链接、框架类型的菜单所属父菜单的类型只能是目录");
                } else if (MenuCategory.BUTTON.name().equalsIgnoreCase(sysMenu.getType())) {
                    if (!MenuCategory.DIRECTORY.name().equalsIgnoreCase(parentMenu.getType())
                            && !MenuCategory.MENU.name().equalsIgnoreCase(parentMenu.getType()))
                        throw new ServiceException("按钮类型的菜单所属父菜单的类型只能是目录或菜单");
                }
            }

            if (!sysMenu.getHidden().equals(dbSysMenu.getHidden())) {
                if (MenuCategory.BUTTON.name().equalsIgnoreCase(dbSysMenu.getType()))
                    throw new ServiceException(sysMenu.getName() + "该菜单为按钮类型，不能取消隐藏");
                // 如果有子菜单不能修改隐藏
                if (sysMenu.getHidden() && count(new LambdaQueryWrapper<SysMenu>().in(SysMenu::getParentId, sysMenu.getId())) > 0)
                    throw new ServiceException(sysMenu.getName() + "该菜单下还存在子菜单，不能隐藏");
            }

            // 当修改了启用状态，并且是不启用得情况下
            if (!sysMenu.getEnabled().equals(dbSysMenu.getEnabled()) && !sysMenu.getEnabled()) {
                // 如果有子菜单不能修改状态
                if (count(new LambdaQueryWrapper<SysMenu>().in(SysMenu::getParentId, sysMenu.getId())) > 0)
                    throw new ServiceException(sysMenu.getName() + "该菜单下还存在子菜单，不能修改状态为禁用");
                // 如果被用户关联不能修改状态
                if (sysRoleMenuService.count(new LambdaQueryWrapper<SysRoleMenu>().in(SysRoleMenu::getMenuId, sysMenu.getId())) > 0)
                    throw new ServiceException(sysMenu.getName() + "该菜单已被角色关联，不能修改状态为禁用");
            }
            BeanUtil.copyProperties(sysMenu, dbSysMenu, true);
            return dbSysMenu;
        }
    }

    /**
     * 删除前校验
     *
     * @param idList 主键列表
     * @return 主键列表
     */
    private List<String> delValid(List<String> idList) {
        if (ObjectUtil.isEmpty(idList))
            throw new ServiceException("待校验主键列表为空");
        List<SysMenu> sysMenuList = list(new LambdaQueryWrapper<SysMenu>().in(SysMenu::getId, idList));
        sysMenuList.forEach(sysMenu -> {
            if (ListUtil.toList(Constants.JCX_PROPERTIES.getMenuProperties().getNoDeletedMenuCodes().split(",")).contains(sysMenu.getCode()))
                throw new ServiceException(sysMenu.getName() + "该菜单为系统内置，不能删除");
        });
        if (count(new LambdaQueryWrapper<SysMenu>().in(SysMenu::getParentId, idList)) > 0)
            throw new ServiceException("尚有菜单下存在子菜单，不能删除");
        if (sysRoleMenuService.count(new LambdaQueryWrapper<SysRoleMenu>().in(SysRoleMenu::getMenuId, idList)) > 0)
            throw new ServiceException("尚有菜单被角色关联，不能删除");
        return idList;
    }

    /**
     * 渲染视图
     *
     * @param sysMenuVoList 视图列表
     * @return 渲染后的视图列表
     */
    private List<SysMenuVo> renderSysMenuVoList(List<SysMenuVo> sysMenuVoList) {
        if (ObjectUtil.isNotEmpty(sysMenuVoList)) {
            // todo 扩展信息填入
            List<SysPowerVo> sysPowerVoList = sysPowerService.voList(new LambdaQueryWrapper<SysPower>().in(SysPower::getMenuId, sysMenuVoList.stream().map(SysMenuVo::getId).collect(Collectors.toList())));
            if (ObjectUtil.isNotEmpty(sysPowerVoList))
                sysMenuVoList.forEach(sysMenuVo -> sysMenuVo.setSysPowerVoList(sysPowerVoList.stream().filter(sysPowerVo -> sysPowerVo.getMenuId().equals(sysMenuVo.getId())).collect(Collectors.toList())));
        }
        return sysMenuVoList;
    }

    /**
     * 构建查询条件
     *
     * @param querySysMenu 查询用业务对象
     * @return 查询条件
     */
    private MPJLambdaWrapper<SysMenu> buildQueryWrapper(QuerySysMenu querySysMenu) {
        MPJLambdaWrapper<SysMenu> lambdaQueryWrapper = new MPJLambdaWrapper<SysMenu>().selectAll(SysMenu.class);
        lambdaQueryWrapper.eq(StrUtil.isNotEmpty(querySysMenu.getId()), SysMenu::getId, querySysMenu.getId());
        lambdaQueryWrapper.in(ObjectUtil.isNotEmpty(querySysMenu.getIdList()), SysMenu::getId, querySysMenu.getIdList());
        lambdaQueryWrapper.eq(StrUtil.isNotEmpty(querySysMenu.getNo()), SysMenu::getNo, querySysMenu.getNo());
        lambdaQueryWrapper.like(StrUtil.isNotEmpty(querySysMenu.getCode()), SysMenu::getCode, querySysMenu.getCode());
        lambdaQueryWrapper.like(StrUtil.isNotEmpty(querySysMenu.getName()), SysMenu::getName, querySysMenu.getName());
        lambdaQueryWrapper.eq(StrUtil.isNotEmpty(querySysMenu.getIcon()), SysMenu::getIcon, querySysMenu.getIcon());
        lambdaQueryWrapper.eq(StrUtil.isNotEmpty(querySysMenu.getPath()), SysMenu::getPath, querySysMenu.getPath());
        lambdaQueryWrapper.eq(StrUtil.isNotEmpty(querySysMenu.getType()), SysMenu::getType, querySysMenu.getType());
        lambdaQueryWrapper.eq(StrUtil.isNotEmpty(querySysMenu.getCategory()), SysMenu::getCategory, querySysMenu.getCategory());
        lambdaQueryWrapper.eq(StrUtil.isNotEmpty(querySysMenu.getComponent()), SysMenu::getComponent, querySysMenu.getComponent());
        lambdaQueryWrapper.eq(StrUtil.isNotEmpty(querySysMenu.getParentId()), SysMenu::getParentId, querySysMenu.getParentId());
        lambdaQueryWrapper.eq(ObjectUtil.isNotEmpty(querySysMenu.getSort()), SysMenu::getSort, querySysMenu.getSort());
        lambdaQueryWrapper.eq(ObjectUtil.isNotEmpty(querySysMenu.getHidden()), SysMenu::getHidden, querySysMenu.getHidden());
        lambdaQueryWrapper.eq(ObjectUtil.isNotEmpty(querySysMenu.getEnabled()), SysMenu::getEnabled, querySysMenu.getEnabled());
        lambdaQueryWrapper.eq(StrUtil.isNotEmpty(querySysMenu.getEnabledBy()), SysMenu::getEnabledBy, querySysMenu.getEnabledBy());
        lambdaQueryWrapper.eq(ObjectUtil.isNotEmpty(querySysMenu.getEnabledTime()), SysMenu::getEnabledTime, querySysMenu.getEnabledTime());
        String noTenantMenuCodes = Constants.JCX_PROPERTIES.getMenuProperties().getNoTenantMenuCodes();
        lambdaQueryWrapper.notIn(ObjectUtil.isNotEmpty(querySysMenu.getOnlyPlatform()) ? querySysMenu.getOnlyPlatform() : false, SysMenu::getCode, ListUtil.toList(noTenantMenuCodes.split(",")));
        lambdaQueryWrapper.apply(StrUtil.isNotEmpty(querySysMenu.getScope()), querySysMenu.getScope());
        lambdaQueryWrapper.orderByAsc(SysMenu::getSort);
        return lambdaQueryWrapper;
    }

}